require 'forwardable'
require 'json'
require 'rack'
module ProxyPump
  class API

    class Router

      attr_reader :routes

      def initialize
        @routes = Hash.new
      end

      %w(get post put delete head patch options).each do |verb|
        class_eval <<-RUBY, __FILE__, __LINE__ + 1
          def #{verb}(path, &block)
            route('#{verb}', path, &block)
          end
        RUBY
      end

      def [](verb)
        verb = upcase_symbol_verb verb
        routes[verb]
      end

      def fetch(verb, default = nil, &block)
        verb = upcase_symbol_verb verb
        routes.fetch(verb, default, &block)
      end

      private

        def route(verb, path, &block)
          verb = upcase_symbol_verb verb
          @routes[verb] ||= {}
          @routes[verb][path] = block
        end

        def upcase_symbol_verb(verb)
          verb = verb.to_s
          verb.upcase!
          verb.to_sym
        end

    end # Router

    extend Forwardable

    CONTENT_TYPE = 'application/json'.freeze

    def_delegators :router, *%i(get post put delete head patch options)

    attr_reader :router, :request

    def initialize
      @router = Router.new
    end

    def params
      request.params
    end

    def call(env)
      @request  = Rack::Request.new env
      verb      = @request.request_method
      path      = @request.path_info
      handler   = router.fetch(verb, {}).fetch(path, nil)
      if handler
        result = instance_eval(&handler)
        render_200 result
      else
        render_404 verb, path
      end
    end

    private

      def render_404(verb, path)
        [404, { 'ContentType' => CONTENT_TYPE, 'Charset' => 'UTF-8' }, [JSON.dump({ errors: { code: 404, message: "Oops! No route for #{verb} #{path}" } })]]
      end

      def render_200(result)
        json =  case result
                when String
                  result = JSON.parse(result) rescue result
                  if result.respond_to?(:to_h)
                    JSON.generate(result).force_encoding(Encoding::UTF_8)
                  else
                    result.force_encoding(Encoding::UTF_8)
                  end
                when Hash
                  JSON.generate(result).force_encoding(Encoding::UTF_8)
                else
                  if result.respond_to?(:call)
                    result.call
                  else
                    result
                  end
                end
        [200, { 'ContentType' => CONTENT_TYPE, 'Charset' => 'UTF-8' }, [json]]
      end

  end # API
end # ProxyPump
